package com.regret.medicine;

import com.github.shyiko.mysql.binlog.BinaryLogFileReader;
import com.github.shyiko.mysql.binlog.event.*;
import com.github.shyiko.mysql.binlog.event.deserialization.EventDeserializer;
import com.mysql.cj.util.StringUtils;
import com.regret.medicine.type.Column;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;

public class BinlogParser {

    public static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private String ip;
    private String port;
    private String databaseName;
    private String username;
    private String password;
    private String tableName;

    private Date startTime;
    private Date endTime;


    private final Map<Long, TableMapEventData> tableNameMap = new HashMap<>();
    private final Map<String, List<Column>> tableColumnNameMap = new HashMap<>();

    public BinlogParser() {
    }

    public BinlogParser(String ip, String port, String databaseName, String username, String password, String tableName, String startTime, String endTime) throws ParseException {
        this.ip = ip;
        this.port = port;
        this.databaseName = databaseName;
        this.username = username;
        this.password = password;
        this.tableName = tableName;

        if (!StringUtils.isNullOrEmpty(startTime)) {
            this.startTime = BinlogParser.simpleDateFormat.parse(startTime);
        }

        if (!StringUtils.isNullOrEmpty(endTime)) {
            this.endTime = BinlogParser.simpleDateFormat.parse(endTime);
        }
    }

    public List<String> parse(File binlogFile) throws IOException, SQLException, ClassNotFoundException {

        EventDeserializer eventDeserializer = new EventDeserializer();

        eventDeserializer.setCompatibilityMode(EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG, EventDeserializer.CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY);
        BinaryLogFileReader reader = new BinaryLogFileReader(binlogFile, eventDeserializer);

        List<Event> events = new ArrayList<>();
        try (reader) {
            for (Event event; (event = reader.readEvent()) != null; ) {
                events.add(event);

                EventData eventData = event.getData();

                if (eventData instanceof TableMapEventData tableMapEventData) {
                    if (!tableNameMap.containsKey(tableMapEventData.getTableId())) {
                        tableNameMap.put(tableMapEventData.getTableId(), tableMapEventData);
                    }
                }
            }

            binlogFile = null;
        }

        getDatabaseTableInfo();

        List<String> sqlList = new ArrayList<>();

        events.forEach(event -> {
            EventData data = event.getData();

            if (Objects.nonNull(startTime) && event.getHeader().getTimestamp() < startTime.getTime()) {
                return;
            }

            if (Objects.nonNull(endTime) && event.getHeader().getTimestamp() > endTime.getTime()) {
                return;
            }

            if (data instanceof WriteRowsEventData) {
                handleWriteRowsEvent((WriteRowsEventData) data, sqlList);
            } else if (data instanceof UpdateRowsEventData) {
                handleUpdateRowsEvent((UpdateRowsEventData) data, sqlList);
            } else if (data instanceof DeleteRowsEventData) {
                handleDeleteRowsEvent((DeleteRowsEventData) data, sqlList);
            }
        });

        return sqlList;
    }

    private void handleUpdateRowsEvent(UpdateRowsEventData data, List<String> sqlList) {
        // 处理 update 事件
        data.getRows().forEach(entry -> {
            String sql = generateUpdateRollbackSql(tableNameMap.get(data.getTableId()), getColumnList(tableNameMap.get(data.getTableId())), entry.getKey(), entry.getValue());

            if (Objects.nonNull(sql)) {
                sqlList.add(sql);
            }
        });
    }

    private void handleDeleteRowsEvent(DeleteRowsEventData data, List<String> sqlList) {
        // 处理 delete 事件
        List<Serializable[]> rows = data.getRows();
        rows.forEach(row -> {
            String sql = generateInsertRollbackSql(tableNameMap.get(data.getTableId()), getColumnList(tableNameMap.get(data.getTableId())), row);

            if (Objects.nonNull(sql)) {
                sqlList.add(sql);
            }
        });
    }

    private void handleWriteRowsEvent(WriteRowsEventData data, List<String> sqlList) {
        // 处理 insert 事件
        List<Serializable[]> rows = data.getRows();
        rows.forEach(row -> {
            String sql = generateDeleteRollbackSql(tableNameMap.get(data.getTableId()), getColumnList(tableNameMap.get(data.getTableId())), row);

            if (Objects.nonNull(sql)) {
                sqlList.add(sql);
            }
        });
    }


    private String generateInsertRollbackSql(TableMapEventData tableMapEventData, List<Column> columnList, Object[] row) {
        if (columnList.isEmpty()) {
            return null;
        }

        StringBuilder builder = new StringBuilder();
        builder.append("delete from ").append(tableMapEventData.getTable()).append(" where ");
        for (int i = 0; i < row.length; i++) {
            Object value = row[i];
            if (value != null) {
                builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(value).append("'");
                if (i < row.length - 1) {
                    builder.append(" and ");
                }
            }
        }
        builder.append(";");
        return builder.toString();
    }

    private static String generateDeleteRollbackSql(TableMapEventData tableMapEventData, List<Column> columnList, Object[] row) {
        if (columnList.isEmpty()) {
            return null;
        }

        StringBuilder builder = new StringBuilder();
        builder.append("insert into ").append(tableMapEventData.getTable()).append(" values (");
        for (int i = 0; i < row.length; i++) {
            Object value = row[i];
            if (Objects.isNull(value)) {
                builder.append("null");
            } else {
                if (value instanceof byte[]) {
                    builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(new String((byte[]) value, 0, ((byte[]) value).length, StandardCharsets.UTF_8)).append("'");
                } else {
                    builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(columnList.get(i).getStringData(value)).append("'");
                }
            }
            if (i < row.length - 1) {
                builder.append(", ");
            }
        }
        builder.append(");");
        return builder.toString();
    }

    private String generateUpdateRollbackSql(TableMapEventData tableMapEventData, List<Column> columnList, Object[] before, Object[] after) {
        if (columnList.isEmpty()) {
            return null;
        }

        StringBuilder builder = new StringBuilder();
        builder.append("update ").append(tableMapEventData.getTable()).append(" set ");
        for (int i = 0; i < before.length; i++) {
            if (!equals(before[i], after[i])) {
                if (Objects.isNull(before[i])) {
                    builder.append("`").append(i).append("` = null");
                } else {
                    if (before[i] instanceof byte[]) {
                        builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(new String((byte[]) before[i], 0, ((byte[]) before[i]).length, StandardCharsets.UTF_8)).append("'");
                    } else {
                        builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(before[i].toString()).append("'");
                    }
                }

                if (i < before.length - 1) {
                    builder.append(", ");
                }
            }
        }
        builder.append(" where ");
        for (int i = 0; i < before.length; i++) {
            if (equals(before[i], after[i])) {
                if (Objects.isNull(before[i])) {
                    builder.append("`").append(i).append("` is null");
                } else {
                    if (before[i] instanceof byte[]) {
                        builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(new String((byte[]) before[i], 0, ((byte[]) before[i]).length, StandardCharsets.UTF_8)).append("'");
                    } else {
                        builder.append("`").append(columnList.get(i).getColumnName()).append("`='").append(before[i].toString()).append("'");
                    }
                }
                if (i < before.length - 1) {
                    builder.append(" and ");
                }
            }
        }
        builder.append(";");
        return builder.toString();
    }

    private static boolean equals(Object o1, Object o2) {
        if (o1 == null && o2 == null) {
            return true;
        }
        if (o1 == null || o2 == null) {
            return false;
        }
        return o1.equals(o2);
    }

    public List<Column> getColumnList(TableMapEventData tableMapEventData) {
        String key = tableMapEventData.getTableId() + "#" + tableMapEventData.getTable();
        return tableColumnNameMap.getOrDefault(key, new ArrayList<>());
    }

    public void getDatabaseTableInfo() throws ClassNotFoundException, SQLException {
        // 创建一个 JDBC 连接
        Class.forName("com.mysql.cj.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://" + ip + ":" + port + "/" + databaseName, username, password);

        // 获取 DatabaseMetaData 对象
        DatabaseMetaData metaData = conn.getMetaData();

        for (TableMapEventData tableMapEventData : tableNameMap.values()) {
            //优先使用填写的数据库、表
            String databaseName = StringUtils.isNullOrEmpty(this.databaseName) ? tableMapEventData.getDatabase() : this.databaseName;
            String tableName = StringUtils.isNullOrEmpty(this.tableName) ? tableMapEventData.getTable() : this.tableName;

            if (!tableColumnNameMap.containsKey(tableMapEventData.getTableId() + "#" + tableMapEventData.getTable())) {
                tableColumnNameMap.put(tableMapEventData.getTableId() + "#" + tableName, new ArrayList<>());
            }

            // 获取表的列名
            try {
                ResultSet rs = metaData.getColumns(databaseName, null, tableName, "%");

                while (rs.next()) {
                    tableColumnNameMap.get(tableMapEventData.getTableId() + "#" + tableName)
                            .add(new Column(rs.getString(ColumnFieldType.COLUMN_NAME.name), JDBCType.valueOf(rs.getInt(ColumnFieldType.DATA_TYPE.name))));
                }

                // 关闭 ResultSet
                rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }

        conn.close();
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getDatabaseName() {
        return databaseName;
    }

    public void setDatabaseName(String databaseName) {
        this.databaseName = databaseName;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
